package raft

import (
	"sync"
	"time"
	"runtime"
	"log"
	"strings"
	"fmt"
	"github.com/petermattis/goid"
)

const nrHist = 1024

type MutexHist struct {
	tp	int
	lno	int
	gid	int64
	tm	time.Time
}

type SyncMutex struct {
	mu	sync.Mutex
	cond	*sync.Cond
	lno	int
	locked	bool
	me	int
	gid	int64
	hist	[]MutexHist
	hp	int
}

func (sm *SyncMutex)Init(me int) {
	//log.Printf("SyncMutex Init\n")
	sm.cond = sync.NewCond(&sm.mu)
	sm.lno = 0
	sm.me = me
	sm.locked = false
	sm.hist = make([]MutexHist, nrHist)
}

func (sm *SyncMutex)RecHist(tp, lno int, gid int64) {
	p := sm.hp
	sm.hp = (sm.hp + 1) % nrHist
	sm.hist[p].tp = tp
	sm.hist[p].lno = lno
	sm.hist[p].gid = gid
	sm.hist[p].tm = time.Now()
}


func (sm *SyncMutex)DumpHist() {
	log.Printf("Log operation history as follow:\n")
	p := sm.hp
	for i:=0; i<nrHist; i++ {
		p = (p + 1) % nrHist
		if sm.hist[p].tp == 0 {
			continue
		}
		log.Printf("curr_gid=%3d, p=%4d, tp=%d, lno=%4d, gid=%3d, tm=%v\n",
			goid.Get(), p, sm.hist[p].tp, sm.hist[p].lno, sm.hist[p].gid, sm.hist[p].tm)
	}
}

func (sm *SyncMutex)Lock() {
	sm.mu.Lock()
	defer sm.mu.Unlock()
	for sm.locked {
		ch := make(chan bool)
		go func() {
			sm.cond.Wait()
			ch<- true
		}()
		select {
		case <-ch:
		case <-time.After(10*time.Second):
			_, _, line, _ := runtime.Caller(1)
			log.Printf("*Deadlock has been detected, curr_lno=%d, curr_gid=%d, locked_lno=%d, locked_gid=%d, me=%d, tm=%v\n",
				line, goid.Get(), sm.lno, sm.gid, sm.me, time.Now())
			sm.DumpHist()
			panic("deadlock detected")
		}
	}
	_, _, line, _ := runtime.Caller(1)
	sm.lno =  line
	sm.gid = goid.Get()
	sm.locked = true
	sm.RecHist(1, sm.lno, sm.gid)
}

func (sm *SyncMutex)Unlock() {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	sm.locked = false
	sm.lno = 0
	_, _, line, _ := runtime.Caller(1)
	sm.RecHist(2, line, goid.Get())
	sm.cond.Signal()
}

func (sm *SyncMutex)ShouldBeLocked() {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	if !sm.locked {
		_, file, line, _ := runtime.Caller(2)
		log.Printf("Error: mutex should be locked, but it is not. gid=%d\n", goid.Get())
		log.Printf("the caller is at %s:%d\n", file, line)
		panic("should be locked")
	}
}

func (sm *SyncMutex)Show() {
	log.Printf("SyncMutex: me=%d,locked=%v,lno=%d",
		sm.me, sm.locked, sm.lno)
}


const (
	debug = false
)

var (
	TimeStart time.Time
	HasTime bool
	chLog chan string
)

func getPos() string {
	fname := ""
	pc, _, line, _ := runtime.Caller(2)
	fc := runtime.FuncForPC(pc)
	if fc != nil {
		tmp := fc.Name()
		words := strings.Split(tmp, ".")
		fname = fmt.Sprintf("%s:%d", words[len(words)-1], line)
	} else {
		fname = fmt.Sprintf("Null(): %d", line)
	}

	return fname
}

func SprintCmd(cmd interface{}) string {
	c := ""
	switch v := cmd.(type) {
	case string:
		sz := len(v)
		if sz > 4 {
			sz = 4
		}
		c = fmt.Sprintf("%s", v[:sz])
	case int:
		c = fmt.Sprintf("%d", (v%1000))
	default:
	}
	return c
}

func SprintLog(entries []LogEntry) string {
	s := ""
	s += fmt.Sprintf("[")
	n := len(entries)
	for i:=0; i<n; i++ {
		e := entries[i]
		cmd := e.Command
		c := SprintCmd(cmd)
		if i<2 || i>n-3 {
			s += fmt.Sprintf("{%d,%d,%s},", e.Index, e.Term, c)
		} else if n>5 && i==3 {
			s += "..."
		}
	}
	s += fmt.Sprintf("]")
	return s
}

func (rf *Raft) Dbg(format string, a ...interface{}) {
	if !debug {
		return
	}

	if !HasTime {
		TimeStart = time.Now()
		HasTime = true
	}

	td := time.Since(TimeStart)
	tmp := td.Microseconds()
	sec := tmp / 1000000
	us := tmp % 1000000

	var st_name = [3]string{"FL", "CD", "LD"}

	fname := getPos()

	s1 := ""
	if rf.State == LEADER {
		s1 = fmt.Sprintf("\n\t\tNI=%v, MI=%v", rf.NextIndex, rf.MatchIndex)
	}
	log := ""
	s := fmt.Sprintf(format, a...)
	log += fmt.Sprintf("\n%d.%06d %-10s me=%d,tm=%d,st=%s,vf=%d,ka=%d,lg=%d,apld=%d,cmt=%d%s\n\t\tlog=",
		sec, us, fname, rf.me, rf.CurrentTerm, st_name[rf.State],
		rf.VotedFor, rf.NumKeepAlive, len(rf.Log), rf.LastApplied, 
		rf.CommitIndex, s1)
	log += fmt.Sprintf(SprintLog(rf.Log))
	log += fmt.Sprintf("\n\t\t%s", s)
	chLog<-log
}

func dumpLog() {
	for {
		s := <-chLog
		fmt.Printf(s)
	}
}

func (rf *Raft) assert(cond bool) {
	if !cond {
		fname := getPos()
		rf.Dbg("assert\n")
		fmt.Printf("Assertion failed at %s\n", fname)
		panic("assertion failed")
	}
}

func (rf *Raft) assertf(cond bool, format string, a ...interface{}) {
	if !cond {
		fname := getPos()
		rf.Dbg(format, a...)
		fmt.Printf("Assertion failed at %s\n", fname)
		panic("assertion failed")
	}
}



